#!/usr/bin/env python3
"""
实时音频录制与分析模块 - 用于Web监控系统
提供实时音频波形数据和异常检测
"""
import os
import sys
import time
import subprocess
import threading
import wave
import numpy as np
from queue import Queue
from datetime import datetime
import logging

class RealtimeAudioMonitor:
    """实时音频监控类"""

    def __init__(self, recordings_dir="recordings", segment_duration=5):
        """
        初始化音频监控

        Args:
            recordings_dir: 录音文件保存目录
            segment_duration: 每段录音时长(秒)
        """
        self.recordings_dir = recordings_dir
        self.segment_duration = segment_duration
        self.running = False
        self.recording_process = None
        self.data_queue = Queue(maxsize=50)
        self.thread = None
        self.logger = logging.getLogger(__name__)

        # 音频参数
        self.sample_rate = 16000  # 采样率
        self.channels = 1  # 单声道

        # 确保目录存在
        os.makedirs(recordings_dir, exist_ok=True)

        # 基线数据
        self.baseline_energy_levels = []
        self.is_baseline_set = False

        # 线程退出标志
        self._thread_stopped = threading.Event()

    def setup_audio_device(self):
        """配置音频设备音量"""
        try:
            self.logger.info("配置音频设备音量...")
            # 设置录音音量 90%
            subprocess.run(['amixer', 'sset', 'Mic', '90%'],
                         capture_output=True, timeout=2)
            subprocess.run(['amixer', 'sset', 'Capture', '90%'],
                         capture_output=True, timeout=2)
            subprocess.run(['amixer', '-c', '1', 'sset', 'Mic', '90%'],
                         capture_output=True, timeout=2)
            subprocess.run(['amixer', '-c', '1', 'sset', 'Capture', '90%'],
                         capture_output=True, timeout=2)
            self.logger.info("✅ 音频设备配置完成")
        except Exception as e:
            self.logger.warning(f"音频设备配置警告: {e}")

    def analyze_audio_file(self, filepath):
        """
        分析音频文件,提取波形和特征

        Returns:
            dict: {
                'waveform': 波形数据(采样点),
                'energy': RMS能量,
                'peak_amplitude': 峰值振幅,
                'is_anomaly': 是否异常,
                'anomaly_score': 异常分数
            }
        """
        try:
            # 检查文件
            if not os.path.exists(filepath):
                return None

            if os.path.getsize(filepath) < 1000:
                return None

            # 读取WAV文件
            with wave.open(filepath, 'rb') as wav_file:
                frames = wav_file.readframes(wav_file.getnframes())
                sample_rate = wav_file.getframerate()
                channels = wav_file.getnchannels()
                sample_width = wav_file.getsampwidth()

                if not frames or len(frames) == 0:
                    return None

                # 转换为numpy数组
                if sample_width == 2:
                    audio_data = np.frombuffer(frames, dtype=np.int16)
                elif sample_width == 1:
                    audio_data = np.frombuffer(frames, dtype=np.uint8)
                else:
                    return None

                # 处理立体声
                if channels == 2:
                    audio_data = audio_data[::2]

                if len(audio_data) == 0:
                    return None

                # 归一化到-1到1
                max_val = np.iinfo(audio_data.dtype).max
                audio_data = audio_data.astype(np.float32) / max_val

            # 计算音频特征
            energy = np.sqrt(np.mean(audio_data ** 2))  # RMS能量
            peak_amplitude = np.max(np.abs(audio_data))  # 峰值振幅

            # === 改进1: 自适应归一化波形 ===
            # 对波形进行自适应归一化,避免振幅忽大忽小
            if peak_amplitude > 0.001:  # 如果有明显信号
                # 归一化到-0.8到0.8,保留一些余量
                normalized_waveform = audio_data * (0.8 / peak_amplitude)
            else:
                # 如果信号太弱,放大显示
                normalized_waveform = audio_data * 100
                # 限制在-1到1范围
                normalized_waveform = np.clip(normalized_waveform, -1.0, 1.0)

            # 提取波形数据(下采样到200个点用于显示)
            target_points = 200
            if len(normalized_waveform) > target_points:
                step = len(normalized_waveform) // target_points
                waveform = normalized_waveform[::step][:target_points]
            else:
                waveform = normalized_waveform

            # === 改进2: 更适合办公室环境的异常检测 ===
            is_anomaly = False
            anomaly_score = 0.0

            # 建立基线(前10个样本,更稳定)
            if not self.is_baseline_set:
                # 接受更宽范围的能量作为基线
                if 0.0001 <= energy <= 1.0:
                    self.baseline_energy_levels.append(energy)

                if len(self.baseline_energy_levels) >= 10:
                    self.is_baseline_set = True
                    avg_energy = np.mean(self.baseline_energy_levels)
                    std_energy = np.std(self.baseline_energy_levels)
                    self.logger.info(f"音频基线建立完成 - 平均能量: {avg_energy:.4f}, 标准差: {std_energy:.4f}")

                # 基线建立期间返回较低的分数
                anomaly_score = min(energy * 1, 0.3)  # 降低基线期间的分数
                is_anomaly = False  # 基线期间不报异常
            else:
                # 基于基线的改进异常检测
                avg_energy = np.mean(self.baseline_energy_levels)
                std_energy = np.std(self.baseline_energy_levels) + 1e-6

                # 使用标准差来判断异常(更科学)
                # z-score: 离平均值多少个标准差
                z_score = (energy - avg_energy) / std_energy

                # 异常分数: 基于z-score,映射到0-1
                # z_score > 3 表示超过3倍标准差,认为是异常
                anomaly_score = min(max(z_score / 8.0, 0), 1.0)  # 除以8使分数更温和

                # 异常判定: 超过10倍标准差才认为是异常(避免人声误报)
                is_anomaly = z_score > 10.0

                # 额外保护: 如果能量本身很小,不算异常
                if energy < avg_energy * 0.5:
                    is_anomaly = False
                    anomaly_score = min(anomaly_score, 0.3)

            return {
                'waveform': waveform.tolist(),
                'energy': float(energy),
                'peak_amplitude': float(peak_amplitude),
                'is_anomaly': bool(is_anomaly),
                'anomaly_score': float(max(0.1, anomaly_score)),
                'sample_rate': sample_rate,
                'duration': len(audio_data) / sample_rate
            }

        except Exception as e:
            self.logger.error(f"音频分析错误: {e}")
            return None

    def _recording_loop(self):
        """录音循环(在独立线程中运行)"""
        self.logger.info("启动音频录制线程")

        # 配置音频设备
        self.setup_audio_device()

        segment_count = 0

        while self.running:
            try:
                segment_count += 1
                timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
                filename = f"audio_{timestamp}.wav"
                filepath = os.path.join(self.recordings_dir, filename)

                self.logger.info(f"录制音频段 {segment_count}: {filename}")

                # 使用sox录音 (增强版)
                # -t alsa plughw:1 : 使用ALSA驱动,设备1
                # trim 0 00:00:05 : 录制5秒
                # gain 6 : 增加6dB增益
                # rate 16000 : 重采样到16kHz
                cmd = [
                    'sox', '-t', 'alsa', 'plughw:1',
                    filepath,
                    'trim', '0', f'00:00:{self.segment_duration:02d}',
                    'gain', '6',
                    'rate', '16000'
                ]

                # 启动录音进程
                self.recording_process = subprocess.Popen(
                    cmd,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    text=True
                )

                # 等待录音完成
                stdout, stderr = self.recording_process.communicate(timeout=self.segment_duration + 5)

                if not self.running:
                    break

                # 检查录音文件
                if os.path.exists(filepath):
                    file_size = os.path.getsize(filepath)

                    if file_size > 1000:
                        self.logger.info(f"✅ 录音完成: {filename} ({file_size} bytes)")

                        # 分析音频
                        analysis = self.analyze_audio_file(filepath)

                        if analysis:
                            analysis['filename'] = filename
                            analysis['filepath'] = filepath
                            analysis['timestamp'] = timestamp

                            # 放入队列
                            if not self.data_queue.full():
                                self.data_queue.put(analysis)
                            else:
                                self.logger.warning("音频数据队列已满,丢弃旧数据")
                                try:
                                    self.data_queue.get_nowait()
                                    self.data_queue.put(analysis)
                                except:
                                    pass
                    else:
                        self.logger.warning(f"录音文件过小: {filename}")
                else:
                    self.logger.error(f"录音文件未创建: {filename}")
                    if stderr:
                        self.logger.error(f"Sox错误: {stderr}")

            except subprocess.TimeoutExpired:
                self.logger.error("录音超时")
                if self.recording_process:
                    self.recording_process.kill()
            except Exception as e:
                self.logger.error(f"录音错误: {e}")
                import traceback
                traceback.print_exc()
                time.sleep(1)

        self._thread_stopped.set()

    def start(self):
        """启动音频监控"""
        if self.running:
            self.logger.warning("音频监控已在运行中")
            return

        # 重置退出标志
        self._thread_stopped.clear()

        self.running = True
        self.thread = threading.Thread(target=self._recording_loop, daemon=False)
        self.thread.start()
        self.logger.info("音频监控已启动")

    def stop(self):
        """停止音频监控"""
        if not self.running:
            return

        self.running = False

        # 终止录音进程
        if self.recording_process and self.recording_process.poll() is None:
            try:
                self.recording_process.terminate()
                self.recording_process.wait(timeout=2)
            except Exception as e:
                try:
                    self.recording_process.kill()
                except:
                    pass

        # 等待线程完全退出
        if self.thread and self.thread.is_alive():
            self._thread_stopped.wait(timeout=10)
            self.thread.join(timeout=2)

        self.thread = None

    def get_data(self, timeout=1.0):
        """
        从队列获取音频分析数据

        Returns:
            dict or None: 音频分析结果
        """
        try:
            return self.data_queue.get(timeout=timeout)
        except:
            return None

    def is_running(self):
        """检查是否正在运行"""
        return self.running


if __name__ == "__main__":
    # 测试代码
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(levelname)s - %(message)s'
    )

    print("=" * 60)
    print("实时音频监控测试")
    print("=" * 60)

    monitor = RealtimeAudioMonitor(recordings_dir="test_recordings", segment_duration=5)
    monitor.start()

    print("\n开始录制音频并分析...\n")

    while True:
        data = monitor.get_data(timeout=1.0)
        if data:
            print(f"\n📊 音频段: {data['filename']}")
            print(f"   时长: {data['duration']:.2f}秒")
            print(f"   能量: {data['energy']:.4f}")
            print(f"   峰值: {data['peak_amplitude']:.4f}")
            print(f"   异常分数: {data['anomaly_score']:.3f}")
            print(f"   是否异常: {'⚠️ 是' if data['is_anomaly'] else '✅ 否'}")
            print(f"   波形点数: {len(data['waveform'])}")

        time.sleep(0.1)

    monitor.stop()
    print("测试完成")
